package project.server.handlers;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Iterator;

import project.server.handlers.Sessions;
import project.server.handlers.OutboundMessages;
import project.server.util.Logger;
import project.shared.player.Player;
import project.server.handlers.Client;
import project.shared.messages.AnswerMessage;
import project.shared.messages.IncorrectNameMessage;
import project.shared.messages.Message;
import project.shared.messages.NameOkMessage;

/**
 * Hoiustab informatsiooni ühe kliendi sessiooni kohta.
 * @author Rauno Veberson
 *
 */
public class Client extends Thread {
	/** Socket mida kasutatakse kliendi ühendamiseks. */
	private Socket socket;

	/** Nimekiri ühendunud klientidest. */
	private Sessions activeSessions;
	
	private OutboundMessages out;

	/** Sissetulevate sõnumite lugeja. */
	private BufferedReader netIn;

	/** Väljaminevate objektide striim */
	private ObjectOutputStream netOut;

	/** Jah/ei väärtus märkimaks, kas klient on hetkel mängus. */
	private boolean inGame = false;

	/** Tõene, kui mängija on valmis mängu alustama. */
	private boolean ready = false;

	/** Logger tegevuste logimiseks. */
	private Logger logger;

	/** Mängija, kes on seotud antud sessiooniga. */
	public Player p;
	
	/**
	 * Instantiates a new ClientSession.
	 * 
	 * @param socket
	 *            the socket used for connecting the client
	 * @param activeSessions
	 *            the list of connected clients
	 * @param logger
	 *            the logger for logging server and error messages
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public Client(Socket socket, Sessions sessions, OutboundMessages outQueue, Logger logger) throws IOException{
		this.socket = socket;
		this.activeSessions = sessions;
		this.logger = logger;
		this.out = outQueue;
		start();
	}
	
	public void run() {
		try {
			netOut = new ObjectOutputStream(new BufferedOutputStream(
					socket.getOutputStream()));
			netOut.flush();
			netIn = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));
		} catch (IOException e1) {
			logger.logError("Error creating datastreams. In thread: " + this);
			activeSessions.removeFromList(this);
		}
		
		logger.log("ClientSession " + this + " starts...");
		while (true) {
			String nameMsg = "";
			try {
				nameMsg = netIn.readLine().trim();
			} catch (IOException e) {
				logger.logError("Error reading from client. Client disconnected");
				closeSocket();
			}
			if (nameMsg != "") {
				if (!nameCheck(nameMsg)) {
					sendMessage(new IncorrectNameMessage());
					System.out.println("nameCheck paneb segast!");
					logger.logError("Incorrect name message");
					closeSocket();
					break;
				} else {
					sendMessage(new NameOkMessage());
					logger.log("Name ok in thread: " + this);
					this.setName(nameMsg);
					break;
				}
			}
		}
		
		try {
			activeSessions.addSession(this);
			while (true) {
				String msg = "";
				try {

					msg = netIn.readLine().trim();

					if (msg.length() > 0) {
						if (!answerCheck(msg)) {
							System.out.println("Vale vastus " + msg + " vale");
							logger.logError("Incorrect answer message");
							System.out.println(msg);
							//break;
						} else {
							System.out.println("else" + msg);
							out.addMessage(new AnswerMessage(msg));
							//sendMessage(new AnswerMessage(msg));
							System.out.println(msg);
							logger.log("Sent message " + msg + " in thread: " + this);

							//break;
						}
					}

				} catch (IOException e) {
					// if player disconnects before the game starts, but all
					// player slots are already filled
					if (p != null) {
						p.disconnected = true;
						ready = true;
					}
					System.out.println(p.getName() + " CS");
					System.out.println(p.disconnected + " CS");
					logger.logError("Error reading from client. Client disconnected");
					closeSocket();
					break;

				}

			}
		} finally {
			closeSocket();
			System.out.println("finally");
			logger.log("Thread " + this + " disconnected");
		}
	}
	
	private boolean answerCheck(String answerMsg) {
		if (answerMsg.equalsIgnoreCase("a") || answerMsg.equalsIgnoreCase("b")) {
			return true;
		} else
			return false;

	}
	
	/**
	 * Name check - checks if the name is already in use
	 * 
	 * @param nameMsg
	 *            the entered name
	 * @return true, if name is not in use
	 */
	private boolean nameCheck(String nameMsg) {
		synchronized (activeSessions) {
			Iterator<Client> itr = activeSessions.iterator();
			while (itr.hasNext()) {
				Client cli = itr.next();
				if (nameMsg.equals(cli.getName())) {
					return false;
				}
			}
			return true;
		}
	}
	
	/**
	 * Send message to the client.
	 * 
	 * @param msg
	 *            the message to be sent
	 */
	protected void sendMessage(Message msg) {
		try {
			if (!socket.isClosed()) {
				System.out.println("CS sendmessage " + netOut.toString());
				System.out.println("CS sendmessage2 " + msg.toString());
				netOut.writeObject(msg);
				String mkk = msg.toString();
				System.out.println(mkk);
				netOut.flush();
			} else {
				throw new IOException();
			}
		} catch (IOException eee) {
			closeSocket();
		}
	}
	
	/**
	 * Closes the socket and removes the ClientSession from the active sessions
	 * list.
	 */
	private void closeSocket() {
		activeSessions.removeFromList(this);
		try {
			socket.close();
		} catch (IOException ee) {
			logger.log("Socket closed in thread: " + this);
		}
	}
	
	/**
	 * Gets the boolean value whether the clientsession is currently used in
	 * some game.
	 * 
	 * @return the value of inGame
	 */
	public boolean getInGame() {
		return inGame;
	}

	/**
	 * Sets the value of the inGame to true.
	 */
	public void setInGame() {
		inGame = true;
	}

	/**
	 * Gets the boolean value whether the clientsession is ready to start the
	 * game.
	 * 
	 * @return the value of ready
	 */
	public boolean getReady() {
		return ready;
	}
}
